package Biblioteca;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Properties;
import java.util.logging.LogManager;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JProgressBar;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.AudioHeader;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.KeyNotFoundException;
import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.TagException;

/**
 * Clase que implementa la biblioteca de música.
 *
 */
public class Biblioteca {

    /**
     * Constante para elementos descoconidos. (Género,Artista,Álbum).
     */
    final String cadenaDesconocido = "Desconocido";
    /**
     * Cadena para separar artista de álbum en las claves de esta tabla hash.
     */
    final String cadenaEspecial = "/()/";
    /**
     * ArrayList con los paths de las canciones en canciones
     */
    private ArrayList<String> indiceCanciones;
    /**
     * ArrayList con las claves de los álbumes en albumes
     */
    private ArrayList<String> indiceAlbumes;
    /**
     * ArrayList con los nombres de los artistas en artistas
     */
    private ArrayList<String> indiceArtistas;
    /**
     * ArrayList con los nombres de los géneros en generos
     */
    private ArrayList<String> indiceGeneros;
    //-----------------------------------------------------
    /**
     * HashMap con los objetos Cancion de la biblioteca.
     * Clave: path de la canción.
     */
    private HashMap<String, Cancion> canciones;
    /**
     * HashMap con los objetos Album de la biblioteca.
     * Clave: nombreDelArtista+caracterEspecial+título del álbum.
     * Porque dos álbumes pueden tener el mismo nombre y ser de diferentes artistas.
     */
    private HashMap<String, Album> albumes;
    /**
     * HashMap con los objetos Artista de la biblioteca.
     * Clave: nombre del artista.
     */
    private HashMap<String, Artista> artistas;
    /**
     * HashMap con los objetos Genero de la biblioteca.
     * Clave: nombre del género.
     */
    private HashMap<String, Genero> generos;

    /**
     * Clase privada de biblioteca que implementa la tabla que relaciona géneros con un número en concreto.
     * Esta tabla se carga de un archivo de texto.
     */
    private class GenerosId3 {

        /**
         * ArrayList que contiene la lista de géneros. En cada posición contiene el género asociado a ese número.
         */
        private String[] listaGeneros;
        /**
         * Indica si la lista de géneros ha sido cargada o no.
         */
        private boolean cargado;

        /**
         * Constructora de la clase privada. Carga la lista de géneros del archivo de texto indicado mediante su ruta.
         * @param rutaArchivoGeneros Ruta del archivo de texto que contiene los géneros.
         */
        public GenerosId3(String rutaArchivoGeneros) {
            try {
                BufferedReader in = new BufferedReader(new FileReader(rutaArchivoGeneros));



                String numero = null;
                String nombre = null;
                Integer posicion;

                int contador = 0;
                String linea = in.readLine();
                while (linea != null) {
                    contador++;
                    linea = in.readLine();
                }
                in.close();
                listaGeneros = new String[contador];
                in = null;
                in = new BufferedReader(new FileReader(rutaArchivoGeneros));
                linea = in.readLine();
                while (linea != null) {
                    try {
                        if (linea.indexOf('.') > -1) {
                            nombre = linea.substring(linea.indexOf('.') + 1, linea.length());
                            numero = linea.substring(0, linea.indexOf('.'));
                            posicion = Integer.parseInt(numero);
                            this.listaGeneros[posicion] = nombre;
                            linea = in.readLine();
                        }
                    } catch (NumberFormatException e) {
                        //La línea no es correcta, se omite.
                        linea = in.readLine();
                    }
                }
                this.cargado = true;

            } catch (FileNotFoundException e) {
                this.cargado = false;
            } catch (IOException e) {
                this.cargado = false;
            }
        }

        /**
         * Devuelve el nombre del género asociado al número posicion
         * @param posicion Numero que representa al género
         * @return Nombre del genero asociado a posicion
         */
        public String getGenero(int posicion) {
            if (posicion >= 0 && posicion < listaGeneros.length) {
                return this.listaGeneros[posicion];
            } else {
                return null;
            }

        }

        /**
         * Devuelve el nombre del género asociado al String pasado en posicion.
         * Posicion debe tener el formato (NUMERO)
         * @param posicion String con el formato (NUMERO)
         * @return Nombre del género asociado al número entre paréntesis.
         */
        public String getGenero(String posicion) {
            int empieza;
            int termina;
            empieza = posicion.indexOf('(');
            termina = posicion.indexOf(')');
            if ((empieza > -1) && (termina > -1)) {
                String numero = posicion.substring(empieza + 1, termina);
                Integer pos;
                try {
                    pos = Integer.parseInt(numero);
                    return this.getGenero(pos);
                } catch (NumberFormatException e) {
                    return null;
                }
            } else {
                return null;
            }
        }

        public boolean listaCargada() {
            return this.cargado;
        }
    }
    private GenerosId3 tablaGeneros;

//-----------------------------------------------------
    /**
     * Constructora de la clase. Construye una biblioteca vacía.
     * @param rutaArchivoGeneros lista de equivalencias entero string
     * correspondientes a generos musicales
     */
    public Biblioteca(String rutaArchivoGeneros) {
        //Se construyen todos los atributos.
        //Colecciones de objetos
        this.albumes = new HashMap<String, Album>();
        this.artistas = new HashMap<String, Artista>();
        this.generos = new HashMap<String, Genero>();
        this.canciones = new HashMap<String, Cancion>();
        //Índices
        this.indiceAlbumes = new ArrayList<String>();
        this.indiceArtistas = new ArrayList<String>();
        this.indiceCanciones = new ArrayList<String>();
        this.indiceGeneros = new ArrayList<String>();
        this.tablaGeneros = new GenerosId3(rutaArchivoGeneros);
        try {
            LogManager.getLogManager().readConfiguration(new ByteArrayInputStream("org.jaudiotagger.level=OFF".getBytes()));
        } catch (IOException e) {
        }

    }

    /**
     * Devuelve un ArrayList con las claves de las canciones contenidas en canciones.
     * @return Claves de canciones
     */
    public ArrayList<String> getIndCanciones() {
        return this.indiceCanciones;
    }

    /**
     * Devuelve un ArrayList con las claves de los artistas contenidas en artistas.
     * @return Claves de artistas
     */
    public ArrayList<String> getIndArtistas() {
        return this.indiceArtistas;
    }

    /**
     * Devuelve un ArrayList con las claves de los géneros contenidas en generos.
     * @return Claves de generos
     */
    public ArrayList<String> getIndGeneros() {
        return this.indiceGeneros;
    }

    /**
     * Devuelve un ArrayList con las claves de los álbumes contenidas en albumes.
     * @return Claves de albumes
     */
    public ArrayList<String> getIndAlbumes() {
        return this.indiceAlbumes;
    }

    /**
     * Devuelve la tabla de objetos Cancion
     * @return tabla de canciones
     */
    public HashMap<String, Cancion> getCanciones() {
        return this.canciones;
    }

    /**
     * Devuelve la tabla de objetos Genero
     * @return tabla de géneros
     */
    public HashMap<String, Genero> getGeneros() {
        return this.generos;
    }

    /**
     * Devuelve la tabla de objetos Artista
     * @return tabla de artistas
     */
    public HashMap<String, Artista> getArtistas() {
        return this.artistas;
    }

    /**
     * Devuelve la tabla de objetos Album
     * @return tabla de albumes
     */
    public HashMap<String, Album> getAlbumes() {
        return this.albumes;
    }

    /**
     * Construye un artista y lo añade a la biblioteca.
     * Antes de ser llamada hay que comprobar que no exista.
     * @param nombreArtista
     * @return el artista construido
     */
    public Artista addArtista(String nombreArtista) {
        Artista artista;
        artista = new Artista(nombreArtista);
        this.artistas.put(nombreArtista, artista);
        this.indiceArtistas.add(nombreArtista);
        return artista;
    }

    /**
     * Construye un álbum y lo añade a la biblioteca.
     * Antes de ser llamada hay que comprobar que no exista.
     * @param nombreAlbum
     * @param nombreArtista
     * @return El álbum construido
     */
    public Album addAlbum(String nombreAlbum, String nombreArtista) {
        Album album;
        album = new Album(nombreAlbum);
        this.albumes.put(nombreArtista + cadenaEspecial + nombreAlbum, album);
        this.indiceAlbumes.add(nombreArtista + cadenaEspecial + nombreAlbum);
        return album;
    }

    /**
     * Construye un género y lo añade a la biblioteca.
     * Antes de ser llamada hay que comprobar que no exista.
     * @param nombreGenero
     * @return el Genero construido
     */
    public Genero addGenero(String nombreGenero) {
        Genero genero;
        genero = new Genero(nombreGenero);
        this.generos.put(nombreGenero, genero);
        this.indiceGeneros.add(nombreGenero);
        return genero;
    }

    /**
     * Devuelve un genero.
     * Un genero tiene una lista de sus canciones y un nombre.
     * @param nombreGenero nombre del género buscado
     * @return el objeto Genero buscado o null si no existe.
     */
    public Genero getGenero(String nombreGenero) {
        return this.generos.get(nombreGenero);
    }

    /**
     * Devuelve un artista.
     * Un Artista tiene una lista de sus álbumes, otra de sus canciones y un nombre.
     * @param nombreArtista nombre del artista buscado
     * @return el objeto Artista buscado o null si no existe.
     */
    public Artista getArtista(String nombreArtista) {
        return this.artistas.get(nombreArtista);
    }

    /**
     * Devuelve un Album.
     * Un Album tiene una lista de sus canciones y un nombre.
     * @param nombreAlbum nombre del Album buscado
     * @param nombreArtista nombre del artista buscado
     * @return el objeto Album buscado o null si no existe.
     */
    public Album getAlbum(String nombreAlbum, String nombreArtista) {
        return this.albumes.get(nombreArtista + this.cadenaEspecial + nombreAlbum);
    }

    /**
     * Devuelve una  canción dada su ruta.
     * @param rutaCancion
     * @return el objeto Canción.
     */
    public Cancion getCancion(String rutaCancion) {
        return this.canciones.get(rutaCancion);
    }

    /**
     * Carga una canción dado un archivo.
     * @param archivo
     * @return Cierto si se ha añadido, falso si no existe el archivo o ya existía en la biblioteca.
     */
    public boolean cargarCancionDeArchivo(File archivo) {
        Cancion cancion;
        Artista artista;
        Album album;
        Genero genero;
        String nombreArtista;
        String nombreAlbum;
        String nombreGenero;
        String titulo;
        String auxiliar = null;

        boolean existiaAlbum;
        if (this.canciones.containsKey(archivo.getAbsolutePath())) {
            //Ya está
            return false;
        }
        try {
            //AudioFile.logger.setLevel(Level.OFF);
            AudioFile f = AudioFileIO.read(archivo);
            Tag tag = f.getTag();
            AudioHeader header = f.getAudioHeader();

            //1.-Leer tags artista, álbum, género.
            //Si alguno no existe (en el archivo), se denomina con cadenaDesconocido.
            nombreArtista = tag.getFirst(FieldKey.ARTIST);
            if (nombreArtista.equals("")) {
                nombreArtista = cadenaDesconocido;
            }
            nombreAlbum = tag.getFirst(FieldKey.ALBUM);
            if (nombreAlbum.equals("")) {
                nombreAlbum = cadenaDesconocido;
            }
            nombreGenero = tag.getFirst(FieldKey.GENRE);

            if (this.tablaGeneros.listaCargada()) {
                auxiliar = this.tablaGeneros.getGenero(nombreGenero);
                if (auxiliar != null) {
                    //Si esto pasa, el género era un número entre paréntesis y en auxiliar está su verdadero nombre.
                    nombreGenero = auxiliar;
                }
            }

            if (nombreGenero.equals("")) {
                nombreGenero = cadenaDesconocido;
            } //2.-Leer título. Si no existe (en el archivo) sustituír con nombre del archivo.
            titulo = tag.getFirst(FieldKey.TITLE);
            if (titulo.equals("")) {
                titulo = archivo.getName();
            } //3.-Buscar el álbum, género y artista en la biblioteca. Si alguno no existe, se crea.
            //Dejar en sus variables.
            album = this.albumes.get(nombreArtista + cadenaEspecial + nombreAlbum);
            if (album == null) {
                existiaAlbum = false;
                album = this.addAlbum(nombreAlbum, nombreArtista);
            } else {
                existiaAlbum = true;
            }
            genero = this.generos.get(nombreGenero);
            if (genero == null) {
                genero = this.addGenero(nombreGenero);
            }
            artista = this.artistas.get(nombreArtista);
            if (artista == null) {
                artista = this.addArtista(nombreArtista);
            }

            //4.-Construír canción.
            //Rellenar su información.
            cancion = new Cancion(archivo.getAbsolutePath(), titulo, artista, album, genero);

            cancion.setTag("duracion", Double.toString(header.getTrackLength()));
            cancion.setTag("biltrate", header.getBitRate());
            cancion.setTag("codec", header.getFormat());
            cancion.setTag("canales", header.getChannels());
            cancion.setTag("año", tag.getFirst(FieldKey.YEAR));
            cancion.setTag("pista", tag.getFirst(FieldKey.TRACK));

            //5.-Añadir la canción a género,artista,album
            genero.addCancion(cancion);
            album.addCancion(cancion);
            artista.addCancion(cancion);
            //Si es un nuevo álbum, además se añade al artista.
            if (!existiaAlbum) {
                artista.addAlbum(album);
            }
            this.canciones.put(archivo.getAbsolutePath(), cancion);
            this.indiceCanciones.add(archivo.getAbsolutePath());
            return true;
        } //Comentado, simplificado a caso Excepcion.Se carga cancion con todo Desconocido.
        /*catch (CannotReadException e) {
        System.out.println("CannotReadException en: " + archivo.getAbsolutePath());
        if(e.getMessage()!=null)System.out.println(e.getMessage());
        if(e.getLocalizedMessage()!=null)System.out.println(e.getLocalizedMessage());
        System.out.println(e.toString());
        return false;
        } catch (TagException e) {
        System.out.println("TagException en: " + archivo.getAbsolutePath());
        if(e.getMessage()!=null)System.out.println(e.getMessage());
        if(e.getLocalizedMessage()!=null)System.out.println(e.getLocalizedMessage());
        System.out.println(e.toString());
        return false;
        } catch (InvalidAudioFrameException e) {
        System.out.println("InvalidAudioFrameException en: " + archivo.getAbsolutePath());
        if(e.getMessage()!=null)System.out.println(e.getMessage());
        if(e.getLocalizedMessage()!=null)System.out.println(e.getLocalizedMessage());
        System.out.println(e.toString());
        return false;
        } catch (ReadOnlyFileException e) {
        System.out.println("ReadOnlyFileException en: " + archivo.getAbsolutePath());
        if(e.getMessage()!=null)System.out.println(e.getMessage());
        if(e.getLocalizedMessage()!=null)System.out.println(e.getLocalizedMessage());
        System.out.println(e.toString());
        return false;
        } catch (KeyNotFoundException e) {
        System.out.println("KeyNotFoundException en: " + archivo.getAbsolutePath());
        if(e.getMessage()!=null)System.out.println(e.getMessage());
        if(e.getLocalizedMessage()!=null)System.out.println(e.getLocalizedMessage());
        System.out.println(e.toString());
        return false;
        } catch(IOException e){
        System.out.println("IOException en: " + archivo.getAbsolutePath());
        if(e.getMessage()!=null)System.out.println(e.getMessage());
        if(e.getLocalizedMessage()!=null)System.out.println(e.getLocalizedMessage());
        System.out.println(e.toString());
        return false;
        } 
         */ catch (Exception e) {
            System.out.println("Exception en: " + archivo.getAbsolutePath());
            if (e.getMessage() != null) {
                System.out.println(e.getMessage());
            }
            if (e.getLocalizedMessage() != null) {
                System.out.println(e.getLocalizedMessage());
            }
            System.out.println(e.toString());

            //1.-Leer tags artista, álbum, género.
            //Todo desconocido
            nombreArtista = cadenaDesconocido;
            nombreAlbum = cadenaDesconocido;
            nombreGenero = cadenaDesconocido;

            //2.-Leer título. Si no existe (en el archivo) sustituír con nombre del archivo.
            //Se coge directamente el nombre del archivo
            titulo = archivo.getName();

            //3.-Buscar el álbum, género y artista en la biblioteca. Si alguno no existe, se crea.
            //Dejar en sus variables.
            //No se modifica del caso ok
            album = this.albumes.get(nombreArtista + cadenaEspecial + nombreAlbum);
            if (album == null) {
                existiaAlbum = false;
                album = this.addAlbum(nombreAlbum, nombreArtista);
            } else {
                existiaAlbum = true;
            }
            genero = this.generos.get(nombreGenero);
            if (genero == null) {
                genero = this.addGenero(nombreGenero);
            }
            artista = this.artistas.get(nombreArtista);
            if (artista == null) {
                artista = this.addArtista(nombreArtista);
            }

            //4.-Construír canción.
            //Rellenar su información.
            cancion = new Cancion(archivo.getAbsolutePath(), titulo, artista, album, genero);
            cancion.setTag("duracion", cadenaDesconocido);
            cancion.setTag("biltrate", cadenaDesconocido);
            cancion.setTag("codec", cadenaDesconocido);
            cancion.setTag("canales", cadenaDesconocido);
            cancion.setTag("año", cadenaDesconocido);
            cancion.setTag("pista", cadenaDesconocido);

            //5.-Añadir la canción a género,artista,album
            genero.addCancion(cancion);
            album.addCancion(cancion);
            artista.addCancion(cancion);
            //Si es un nuevo álbum, además se añade al artista.
            if (!existiaAlbum) {
                artista.addAlbum(album);
            }
            this.canciones.put(archivo.getAbsolutePath(), cancion);
            this.indiceCanciones.add(archivo.getAbsolutePath());
            return true;
        }
    }

    /**
     * Carga la biblioteca desde el directorio o archivo. En caso de ser un archivo .mp3,
     * carga su meta-información y añade el archivo a la biblioteca. Si es un directorio,
     * realiza un recorrido en profundidad del árbol procesando todos los archivos .mp3 que encuentre
     * en el subárbol por debajo del nivel del directorio indicado.
     * @param f Archivo o directorio.
     * @param barraProgreso Si es null se ignora, si no se incrementa en 1 el valor de la barra
     * de progreso con cada archivo tratado.
     */
    public void leerDeDirectorio(File f, JProgressBar barraProgreso) {
        //Si es un directorio, caso Recursivo. Llamar con la lista de archivos.
        if (f.isDirectory()) {
            File[] archivos;
            archivos = f.listFiles();
            for (int i = 0; i < archivos.length; i++) {
                leerDeDirectorio(archivos[i], barraProgreso);
            }
        } else if (f.isFile()) {
            //Si es un archivo, caso Base. Intentar cargar si tiene una extensión válida.
            String nombre = f.getName();
            String extension = nombre.substring(nombre.lastIndexOf("."));
            if (extension.equals(".mp3")) {
                this.cargarCancionDeArchivo(f);
            }
            barraProgreso.setValue(barraProgreso.getValue() + 1);
        }
    }

    /**
     * Utilizado para depurar, muestra de una manera simple la información de la biblioteca
     * por  consola.
     */
    public void imprimirEnConsola() {
        System.out.println("-------");
        System.out.println("Generos");
        System.out.println("-------");
        for (int i = 0; i < this.indiceGeneros.size(); i++) {
            System.out.println(this.generos.get(this.indiceGeneros.get(i)).toString());
        }
        System.out.println("-------");
        System.out.println("Artistas");
        System.out.println("-------");
        for (int i = 0; i < this.indiceArtistas.size(); i++) {
            System.out.println(this.artistas.get(this.indiceArtistas.get(i)).toString());
        }
        System.out.println("-------");
        System.out.println("Albumes");
        System.out.println("-------");
        for (int i = 0; i < this.indiceAlbumes.size(); i++) {
            System.out.println(this.albumes.get(this.indiceAlbumes.get(i)).toString());
        }
        System.out.println("-------");
        System.out.println("Canciones");
        System.out.println("-------");
        for (int i = 0; i < this.indiceCanciones.size(); i++) {
            System.out.println(this.canciones.get(this.indiceCanciones.get(i)).toString());
        }
    }

    /**
     * Utilizado para depurar, vuelca de una manera simple la información de la biblioteca en un
     * archivo de texto.
     * @param ruta Ruta del archivo de texto.
     * @return Cierto si ha habido éxito, falso en caso contrario.
     */
    public boolean imprimirEnTexto(String ruta) {
        try {
            BufferedWriter bf = new BufferedWriter(new FileWriter(ruta, false));
            bf.write("-------\n");
            bf.write("Generos\n");
            bf.write("-------\n");
            for (int i = 0; i < this.indiceGeneros.size(); i++) {
                bf.write(this.generos.get(this.indiceGeneros.get(i)).toString());
            }
            bf.write("-------\n");
            bf.write("Artistas\n");
            bf.write("-------\n");
            for (int i = 0; i < this.indiceArtistas.size(); i++) {
                bf.write(this.artistas.get(this.indiceArtistas.get(i)).toString());
            }
            bf.write("-------\n");
            bf.write("Albumes\n");
            bf.write("-------\n");
            for (int i = 0; i < this.indiceAlbumes.size(); i++) {
                bf.write(this.albumes.get(this.indiceAlbumes.get(i)).toString());
            }
            bf.write("-------\n");
            bf.write("Canciones\n");
            bf.write("-------\n");
            for (int i = 0; i < this.indiceCanciones.size(); i++) {
                bf.write(this.canciones.get(this.indiceCanciones.get(i)).toString());
            }
            bf.write("-------\n");
            bf.write("Resumen\n");
            bf.write("-------\n");

            bf.write("Artistas:\n");
            bf.write("\t Indices: " + this.indiceArtistas.size() + "\n");
            bf.write("\t Objetos: " + this.artistas.size() + "\n");

            bf.write("Albumes:\n");
            bf.write("\t Indices: " + this.indiceAlbumes.size() + "\n");
            bf.write("\t Objetos: " + this.albumes.size() + "\n");

            bf.write("Generos:\n");
            bf.write("\t Indices: " + this.indiceGeneros.size() + "\n");
            bf.write("\t Objetos: " + this.generos.size() + "\n");

            bf.write("Canciones:\n");
            bf.write("\t Indices: " + this.indiceCanciones.size() + "\n");
            bf.write("\t Objetos: " + this.canciones.size() + "\n");
            bf.close();
            return true;
        } catch (Exception e) {
            System.out.println("Imprimir en texto: no se puede abrir o crear el archivo. Ruta: " + ruta);
            return false;
        }
    }

    /**
     * Vuelca la información de la biblioteca en un archivo .xml
     * @param ruta Ruta del archivo .xml. Si no existe, se creará.
     * @return Cierto si ha habido éxito, falso en caso contrario.
     */
    public boolean toXml(String ruta) {
        try {
            BufferedWriter bf = new BufferedWriter(new FileWriter(ruta, false));
            bf.write("<?xml version=\"1.0\"?>\n");
            bf.write("<biblioteca>\n");
            bf.write("\t<generos>\n");
            for (int i = 0; i < this.indiceGeneros.size(); i++) {
                bf.write(this.generos.get(this.indiceGeneros.get(i)).toXml(2));
            }
            bf.write("\t</generos>\n");
            bf.write("\t<artistas numcanciones=\"" + this.indiceCanciones.size() + "\">\n");
            for (int i = 0; i < this.indiceArtistas.size(); i++) {
                bf.write(this.artistas.get(this.indiceArtistas.get(i)).toXml(2));
            }
            bf.write("\t</artistas>\n");
            bf.write("</biblioteca>\n");
            bf.close();
            return true;
        } catch (Exception e) {
            System.out.println("Volcado XML: no se puede abrir o crear el archivo. Ruta: " + ruta);
            return false;
        }
    }

    /**
     * Devuelve un array de canciones cuyo título, artista, álbum o género contienen
     * la cadena aguja.
     * @param aguja Cadena a buscar.
     * @return Array de canciones que cumplen los criterios.
     */
    public Cancion[] buscarTodos(String aguja) {
        Cancion[] resultados;
        ArrayList<Cancion> resultadosAux;
        resultadosAux = new ArrayList<Cancion>();
        Iterator<String> iterador = this.indiceCanciones.iterator();
        Iterator<Cancion> iterador2;
        Cancion actual;
        while (iterador.hasNext()) {
            actual = this.canciones.get(iterador.next());
            if ((actual.toString().toLowerCase().contains(aguja.toLowerCase()))
                    || (actual.getArtista().getNombre().toLowerCase().contains(aguja.toLowerCase()))
                    || (actual.getAlbum().getNombre().toLowerCase().contains(aguja.toLowerCase()))
                    || (actual.getGenero().getNombre().toLowerCase().contains(aguja.toLowerCase()))) {

                resultadosAux.add(actual);
            } else {
            }
        }
        resultados = new Cancion[resultadosAux.size()];
        iterador2 = resultadosAux.iterator();
        int contador = 0;
        while (iterador2.hasNext()) {
            resultados[contador] = iterador2.next();
            contador++;
        }
        return resultados;
    }

    /**
     * Devuelve un array de canciones cuyo álbum contiene la cadena aguja.
     * @param aguja Cadena a buscar.
     * @return Array de canciones que cumplen los criterios.
     */
    public Cancion[] buscarAlbum(String aguja) {
        Cancion[] resultados;
        ArrayList<Cancion> resultadosAux;
        resultadosAux = new ArrayList<Cancion>();
        Iterator<String> iterador = this.indiceAlbumes.iterator();
        Iterator<Cancion> iterador2;
        String actual;
        String auxiliar;
        while (iterador.hasNext()) {
            actual = iterador.next();
            auxiliar = (actual.substring(actual.indexOf(cadenaEspecial) + cadenaEspecial.length(), actual.length()));
            if (auxiliar.toLowerCase().contains(aguja.toLowerCase())) {
                iterador2 = this.albumes.get(actual).canciones.iterator();
                while (iterador2.hasNext()) {
                    resultadosAux.add(iterador2.next());
                }
            }
        }
        resultados = new Cancion[resultadosAux.size()];
        iterador2 = resultadosAux.iterator();
        int contador = 0;
        while (iterador2.hasNext()) {
            resultados[contador] = iterador2.next();
            contador++;
        }
        return resultados;
    }

    /**
     * Devuelve un array de canciones cuyo título contiene la cadena aguja.
     * @param aguja Cadena a buscar.
     * @return Array de canciones que cumplen los criterios.
     */
    public Cancion[] buscarTitulo(String aguja) {
        Cancion[] resultados;
        ArrayList<Cancion> resultadosAux;
        resultadosAux = new ArrayList<Cancion>();
        Iterator<String> iterador = this.indiceCanciones.iterator();
        Iterator<Cancion> iterador2;
        Cancion actual;
        while (iterador.hasNext()) {
            actual = this.canciones.get(iterador.next());
            if (actual.toString().toLowerCase().contains(aguja.toLowerCase())) {
                resultadosAux.add(actual);
            }
        }
        resultados = new Cancion[resultadosAux.size()];
        iterador2 = resultadosAux.iterator();
        int contador = 0;
        while (iterador2.hasNext()) {
            resultados[contador] = iterador2.next();
            contador++;
        }
        return resultados;


    }

    /**
     * Devuelve un array de canciones cuyo género contiene la cadena aguja.
     * @param aguja Cadena a buscar.
     * @return Array de canciones que cumplen los criterios.
     */
    public Cancion[] buscarGenero(String aguja) {
        Cancion[] resultados;
        ArrayList<Cancion> resultadosAux;
        resultadosAux = new ArrayList<Cancion>();
        Iterator<String> iterador = this.indiceGeneros.iterator();
        Iterator<Cancion> iterador2;
        String actual;
        while (iterador.hasNext()) {
            actual = iterador.next();
            if (actual.toLowerCase().contains(aguja.toLowerCase())) {
                iterador2 = this.generos.get(actual).canciones.iterator();
                while (iterador2.hasNext()) {
                    resultadosAux.add(iterador2.next());
                }
            }
        }
        resultados = new Cancion[resultadosAux.size()];
        iterador2 = resultadosAux.iterator();
        int contador = 0;
        while (iterador2.hasNext()) {
            resultados[contador] = iterador2.next();
            contador++;
        }
        return resultados;



    }

    /**
     * Devuelve un array de canciones cuyo artista contiene la cadena aguja.
     * @param aguja Cadena a buscar.
     * @return Array de canciones que cumplen los criterios.
     */
    public Cancion[] buscarArtista(String aguja) {
        Cancion[] resultados;
        ArrayList<Cancion> resultadosAux;
        resultadosAux = new ArrayList<Cancion>();
        Iterator<String> iterador = this.indiceArtistas.iterator();
        Iterator<Cancion> iterador2;
        String actual;
        while (iterador.hasNext()) {
            actual = iterador.next();

            if (actual.toLowerCase().contains(aguja.toLowerCase())) {
                iterador2 = this.artistas.get(actual).canciones.iterator();
                while (iterador2.hasNext()) {
                    resultadosAux.add(iterador2.next());
                }
            }
        }
        resultados = new Cancion[resultadosAux.size()];
        iterador2 = resultadosAux.iterator();
        int contador = 0;
        while (iterador2.hasNext()) {
            resultados[contador] = iterador2.next();
            contador++;
        }
        return resultados;

    }

    /**
     * Elimina una canción dada de la biblioteca. Elimina en cascada, manteniendo
     * la coherencia de las asociaciones entre elementos de la biblioteca.
     * @param cancion Canción de la biblioteca que se desea eliminar.
     * @return cierto si ha habido éxito, falso en caso contrario.
     */
    public boolean eliminarCancion(Cancion cancion) {
        int pos = indiceCanciones.indexOf(cancion.getRuta());
        if (pos == -1) {
            return false;
        }
        Artista artista = cancion.getArtista();
        Genero genero = cancion.getGenero();
        Album album = cancion.getAlbum();

        genero.getCanciones().remove(cancion);
        if (genero.getCanciones().isEmpty()) {
            //Borrar género
            this.indiceGeneros.remove(genero.getNombre());
            this.generos.remove(genero.getNombre());
        }

        album.getCanciones().remove(cancion);
        artista.getCanciones().remove(cancion);
        if (album.getCanciones().isEmpty()) {
            //Borrar album de artista y biblioteca
            artista.getAlbumes().remove(album);
            this.indiceAlbumes.remove(artista.getNombre() + cadenaEspecial + album.getNombre());
            this.albumes.remove(artista.getNombre() + cadenaEspecial + album.getNombre());
            //Sólo si un álbum está vacío puede estarlo un artista.
            if (artista.getAlbumes().isEmpty()) {
                this.indiceArtistas.remove(artista.getNombre());
                this.artistas.remove(artista.getNombre());
            }
        }
        this.indiceCanciones.remove(pos);
        return true;
    }

    /**
     * Método que elimina de la biblioteca las canciones que no estén presentes en disco.
     * Comprueba una por una si existe el archivo en disco.
     */
    public void limpiarCancionesNoPresentes() {
        Iterator<String> i = ((ArrayList<String>) this.indiceCanciones.clone()).iterator();
        String ruta;
        File f;
        while (i.hasNext()) {
            ruta = i.next();
            f = new File(ruta);
            if (!f.exists()) {
                this.eliminarCancion(this.canciones.get(ruta));
            }
        }
    }
}
